package emulator.hardware.nmos6502.commands;

import emulator.EmulatorException;
import emulator.hardware.HwByte;
import emulator.hardware.nmos6502.Command;
import emulator.hardware.nmos6502.CommandSet;
import emulator.hardware.nmos6502.Cpu6502;
import emulator.hardware.nmos6502.Flags6502;
import emulator.hardware.nmos6502.Operand;
import emulator.hardware.nmos6502.operands.Absolute;
import emulator.hardware.nmos6502.operands.AbsoluteX;
import emulator.hardware.nmos6502.operands.AbsoluteY;
import emulator.hardware.nmos6502.operands.Immediate;
import emulator.hardware.nmos6502.operands.IndirectX;
import emulator.hardware.nmos6502.operands.IndirectY;
import emulator.hardware.nmos6502.operands.ZeroPage;
import emulator.hardware.nmos6502.operands.ZeroPageX;

/** ADC (ADd with Carry)
 *  
 *  Affects Flags: S V Z C
 *  
 *  MODE           SYNTAX       HEX LEN TIM
 *  Immediate     ADC #$44      $69  2   2
 *  Zero Page     ADC $44       $65  2   3
 *  Zero Page,X   ADC $44,X     $75  2   4
 *  Absolute      ADC $4400     $6D  3   4
 *  Absolute,X    ADC $4400,X   $7D  3   4+
 *  Absolute,Y    ADC $4400,Y   $79  3   4+
 *  Indirect,X    ADC ($44,X)   $61  2   6
 *  Indirect,Y    ADC ($44),Y   $71  2   5+
 *  
 *  + add 1 cycle if page boundary crossed
 *  
 *  ADC results are dependant on the setting of the decimal flag. In decimal
 *  mode, addition is carried out on the assumption that the values involved
 *  are packed BCD (Binary Coded Decimal).
 *  
 *  There is no way to add without carry.
 */
public class ADC implements Command {

	@Override
	public String getName() {
		return "ADC";
	}
	
	@Override
	public void execute(Cpu6502 cpu, Operand operand) throws EmulatorException {
		int accu_old = (int) cpu.getAccu().getNumber();
		Flags6502 flags = cpu.getFlags();
		
		int op = (int) operand.getByte().getNumber();
		int accu = 0;
		if (flags.getDecimal())
			accu = addDecimal(accu_old, flags, op);
		else
			accu = addBinary(accu_old, flags, op);
		
		cpu.setAccu(new HwByte(accu));
		cpu.setFlags(flags);
	}

	@Override
	public void register(CommandSet cset) {
		cset.defineCommand(0x69, this, new Immediate());
		cset.defineCommand(0x65, this, new ZeroPage());
		cset.defineCommand(0x75, this, new ZeroPageX());
		cset.defineCommand(0x6D, this, new Absolute());
		cset.defineCommand(0x7D, this, new AbsoluteX());
		cset.defineCommand(0x79, this, new AbsoluteY());
		cset.defineCommand(0x61, this, new IndirectX());
		cset.defineCommand(0x71, this, new IndirectY());
	}

	private int addBinary(int accu_in, Flags6502 flags, int op) {
		int accu = accu_in + op;
		if (flags.getCarry())
			accu++;

		if (accu_in > 127) accu_in -= 256;
		if (op > 127) op -= 256;
		int signed_result = accu_in + op;
		if (flags.getCarry())
			signed_result++;
		
		flags.setCarry(accu > 255);
		accu &= 255;
		flags.setNZFromValue(accu);
		flags.setOverflow((signed_result < -128) || (signed_result > 127));
		return accu;
	}

	private int addDecimal(int accu_in, Flags6502 flags, int op) {
		int accu = 0;
		int borrow = (flags.getCarry() ? 1 : 0);
		
		int nibble1 = accu_in & 0x0F;
		int nibble2 = op & 0x0F;
		int sum = nibble1 + nibble2 + borrow;
		borrow = sum / 10;
		sum = sum % 10;
		accu += sum;

		nibble1 = (accu_in & 0xF0) >> 4;
		nibble2 = (op & 0xF0) >> 4;
		sum = nibble1 + nibble2 + borrow;
		borrow = sum / 10;
		sum = sum % 10;
		accu += sum << 4;

		flags.setCarry(borrow > 0);
		flags.setNZFromValue(accu);
		flags.setOverflow(false);
			
		return accu;
	}
}
